<?php
/**
 *+------------------
 * Lflow
 *+------------------
 * Copyright (c) 2023~2030 gitee.com/liu_guan_qing All rights reserved.本版权不可删除，侵权必究
 *+------------------
 * Author: Mr.April(405784684@qq.com)
 *+------------------
 */
declare (strict_types=1);

namespace lflow\workflow;

use lflow\assignment\AssignmentTask;
use lflow\entity\HistoryTask;
use lflow\entity\Task;
use lflow\entity\TaskActor;
use lflow\enum\TaskEnum;
use lflow\helper\ArrayHelper;
use lflow\helper\AssertHelper;
use lflow\helper\DateHelper;
use lflow\helper\JsonHelper;
use lflow\helper\StringHelper;
use lflow\impl\GeneralAccessStrategy;
use lflow\impl\JsonString;
use lflow\ITaskService;
use lflow\model\CustomModel;
use lflow\model\ProcessModel;
use lflow\model\TaskModel;
use lflow\WorkFlowException;
use think\Exception;

/**
 * 任务执行业务类
 *
 * @author Mr.April
 * @since  1.0
 */
class TaskService extends AccessService implements ITaskService
{

    const START = 'start';
    const ADMIN = "lflow.admin";
    const AUTO = "lflow.auto";
    const ID = "lflow.orderNo";

    public function complete(string $taskId, string $operator = null, $args = null): Task
    {

        $task = $this->access()->getTask($taskId);
        AssertHelper::notNull($task, "指定的任务[id=" . $taskId . "]不存在");
        $task->setVariable($args);
        if (!$this->isAllowed($task, $operator)) {
            throw new WorkFlowException("当前参与者[" . $operator . "]不允许执行任务[taskId=" . $taskId . "]");
        }
        $history = new HistoryTask();
        $history->historyTask($task);
        $history->setFinishTime(DateHelper:: getTime());
        $history->setTaskState(self::STATE_FINISH);
        $history->setOperator($operator);
        if (empty($history->getActorIds())) {
            $actors   = $this->access()->getTaskActorsByTaskId($task->getId());
            $actorIds = [];
            for ($i = 0; $i < count($actors); $i++) {
                $actorIds[$i] = $actors[$i]['actor_id'];
            }
            $history->setActorIds($actorIds);
        }
        $this->access()->saveHistory($history);
        $this->access()->deleteTask($task);
        return $task;
    }

    public function createTask(TaskModel $taskModel, Execution $execution): array
    {
        $tasks = [];
        $args  = json_decode($execution->getArgs(), false);
        if ($args == null) {
            $args = new \stdClass();
        }
        $expireDate = DateHelper::processTime($args, $taskModel->getExpireTime());
        $remindDate = DateHelper::processTime($args, $taskModel->getReminderTime());
        $form       = '';
        $actionUrl  = StringHelper::isEmpty($form) ? $taskModel->getForm() : $form;
        $actors     = $this->getTaskActors($taskModel, $execution);
        $task       = $this->createTaskBase($taskModel, $execution);
        $task->setActionUrl('actionUrl', $actionUrl);
        $task->setExpireDate('expireDate', $expireDate);
        $task->setExpireTime('expireTime', $remindDate);
        $task->setVariable(json_encode($args));
        if ($taskModel->isPerformAny()) {
            //任务执行方式为参与者中任何一个执行即可驱动流程继续流转，该方法只产生一个task
            $this->saveTask($task, $actors);
            $tasks[] = $task;
        } else if ($taskModel->isPerformAll()) {
            //任务执行方式为参与者中每个都要执行完才可驱动流程继续流转，该方法根据参与者个数产生对应的task数量
            foreach (explode(',', $actors) as $actor) {
                $singleTask = clone $task;
                $singleTask = $this->saveTask($singleTask, $actor);
                $singleTask->setRemindDate($remindDate);
                $tasks[] = $singleTask;
            }
        }
        return $tasks;
    }

    public function rejectTask(ProcessModel $model, Task $currentTask): Task
    {
        $parentTaskId = $currentTask->getParentTaskId();
        if (StringHelper::isEmpty($parentTaskId)) {
            throw new WorkFlowException("上一步任务ID为空，无法驳回至上一步处理");
        }
        $current = $model->getNode($currentTask->getTaskName());
        $history = $this->access()->getHistTask($parentTaskId);
        $parent  = $model->getNode($history->getTaskName());
        if (!(new TaskModel())->canRejected($current, $parent)) {
            throw new WorkFlowException("无法驳回至上一步处理，请确认上一步骤并非fork、join、suprocess以及会签任务");
        }
        $task = $history->undoTask();
        $task->setId(StringHelper:: getPrimaryKey());
        $task->setCreateTime(DateHelper::getTime());
        $task->setOperator($history->getOperator());
        $this->access()->saveTask($task);
        $this->assignTask($task->getId(), $task->getOperator());
        return $task;
    }

    public function createTaskBase(TaskModel $model, Execution $execution): Task
    {
        $task = new Task();
        $task->setOrderId($execution->getOrder()->getId());
        $task->setTaskName($model->getName());
        $task->setDisplayName($model->getDisplayName());
        $task->setCreateTime(DateHelper::getTime());
        if ($model->isMajor()) {
            $task->setTaskType(array_search('Major', TaskEnum::TaskType));
        } else {
            $task->setTaskType(array_search('Aidant', TaskEnum::TaskType));
        }
        $task->setParentTaskId($execution->getTask() == null ? self::START : $execution->getTask()->getId());
        $task->setModel($model);
        return $task;
    }

    private function saveTask(Task $task, string $actors): Task
    {
        $task->setId(StringHelper::getPrimaryKey());
        $task->setPerformType(array_search('ANY', TaskEnum::PerformType));
        $task->setActorIds((array)$actors);
        $this->access()->saveTask($task);
        $this->assignTask($task->getId(), $actors);
        return $task;
    }

    private function assignTask(string $taskId, string $actorIds): void
    {
        if ($actorIds == null || count(explode(',', $actorIds)) == 0) return;
        foreach (explode(',', $actorIds) as $actorId) {
            if (StringHelper::isEmpty($actorId)) continue;
            $taskActor = new TaskActor();
            $taskActor->setTaskId($taskId);
            $taskActor->setActorId($actorId);
            $this->access()->saveTaskActor($taskActor);
        }
    }

    public function isAllowed(Task $task, string $operator): bool
    {
        if (StringHelper::isNotEmpty($operator)) {
            if (StringHelper::equalsIgnoreCase(self::ADMIN, $operator) || StringHelper::equalsIgnoreCase(self::AUTO, $operator)) {
                return true;
            }
            if (StringHelper::isNotEmpty($task->getOperator())) {
                return StringHelper::equalsIgnoreCase($task->getOperator(), $operator);
            }
        }
        $actors = $this->access()->getTaskActorsByTaskId($task->getId());
        if (empty($actors)) {
            return true;
        }
        return !StringHelper::isEmpty($operator) && GeneralAccessStrategy::isAllowed($operator, (object)$actors);
    }

    public function getTaskActors(TaskModel $model, Execution $execution): string
    {
        return (new AssignmentTask())->assign($model, $execution);
    }

    public function updateTask(Task $task): void
    {
        $this->access()->updateTask($task);
    }

    public function history(Execution $execution, CustomModel $model): HistoryTask
    {
        $historyTask = new HistoryTask();
        $historyTask->setId(StringHelper::getPrimaryKey());
        $historyTask->setOrderId($execution->getOrder()->getId());
        $currentTime = DateHelper::getTime();
        $historyTask->setCreateTime($currentTime);
        $historyTask->setFinishTime($currentTime);
        $historyTask->setDisplayName($model->getDisplayName());
        $historyTask->setTaskName($model->getName());
        $historyTask->setTaskState(self::STATE_FINISH);
//        $historyTask->setTaskType();
        $historyTask->setParentTaskId($execution->getTask() == null ? self::START : $execution->getTask()->getId());
        $historyTask->setVariable(JsonHelper::toJson($execution->getArgs()));
        $this->access()->saveHistory($historyTask);
        return $historyTask;
    }

    public function take(string $taskId, string $operator): Task
    {
        $task = $this->access()->getTask($taskId);
        AssertHelper::notNull($task, "指定的任务[id=" . $taskId . "]不存在");
        if (!isAllowed($task, $operator)) {
            throw new WorkFlowException("当前参与者[" . $operator . "]不允许提取任务[taskId=" . $taskId . "]");
        }
        $task->setOperator($operator);
        $task->setFinishTime(DateHelper:: getTime());
        $this->access()->updateTask($task);
        return $task;
    }

    public function resume(string $taskId, string $operator): Task
    {
        $histTask = access() . getHistTask($taskId);
        AssertHelper::notNull($histTask, "指定的历史任务[id=" . $taskId . "]不存在");
        $isAllowed = true;
        if (StringHelper::isNotEmpty($histTask->getOperator())) {
            $isAllowed = StringHelper::equalsIgnoreCase($histTask->getOperator(), $operator);
        }
        if ($isAllowed) {
            $task = $histTask->undoTask();
            $task->setId(StringHelper:: getPrimaryKey());
            $task->setCreateTime(DateHelper:: getTime());
            $this->access()->saveTask($task);
            $this->assignTask($task->getId(), $task->getOperator());
            return $task;
        } else {
            throw new SnakerException("当前参与者[" . $operator . "]不允许唤醒历史任务[taskId=" . $taskId . "]");
        }
    }

    public function addTaskActor(string $taskId, int $performType, string $actors): void
    {
        $task = $this->access()->getTask($taskId);
        AssertHelper:: notNull($task, "指定的任务[id=" . $taskId . "]不存在");
        if (!$task->isMajor()) return;
        if (empty($performType)) $performType = $task->getPerformType() ?? 0;
        switch ($performType) {
            case 0:
                $this->assignTask($task->getId(), $actors);
                $data                     = $task->getVariableMap();
                $oldActor                 = $data->{Task::$KEY_ACTOR};
                $data->{Task::$KEY_ACTOR} = $oldActor . "," . StringHelper:: getStringByArray($actors);
                $task->setVariable(JsonHelper:: toJson($data));
                $this->access()->updateTask($task);
                break;
            case 1:
                try {
                    foreach (explode(',', $actors) as $actor) {
                        $newTask = clone $task;
                        $newTask->setId(StringHelper::getPrimaryKey());
                        $newTask->setCreateTime(DateHelper::getTime());
                        $newTask->setOperator($actor);
                        $taskData                     = $task->getVariableMap();
                        $taskData->{Task::$KEY_ACTOR} = $actor;
                        $task->setVariable(JsonHelper:: toJson($taskData));
                        $this->access()->saveTask($newTask);
                        $this->assignTask($newTask->getId(), $actor);
                    }
                } catch (\Exception $e) {
                    throw new WorkFlowException("任务对象不支持复制");
                }
                break;
            default :
                break;
        }
    }

    public function removeTaskActor(string $taskId, string $actors): void
    {
        $task = $this->access()->getTask($taskId);
        AssertHelper:: notNull($task, "指定的任务[id=" . $taskId . "]不存在");
        if (empty($actors)) return;
        if ($task->isMajor()) {
            $this->access()->removeTaskActor($task->getId(), $actors);
            $taskData = $task->getVariableMap();
            $actorStr = $taskData->{Task::$KEY_ACTOR};
            if (StringHelper:: isNotEmpty($actorStr)) {
                $actorArray = explode(',', $actorStr);
                foreach ($actorArray as $actor) {
                    $isMatch = false;
                    if (StringHelper:: isEmpty($actor)) continue;
                    foreach (explode(',', $actors) as $actors) {
                        if (StringHelper::equalsIgnoreCase($actor, $actor)) {
                            $isMatch = true;
                            break;
                        }
                    }
                    if ($isMatch) continue;
                    $newActor[] = $actor;
                }
                $taskData->{Task::$KEY_ACTOR} = $newActor ?? [];
                $task->setVariable(JsonHelper:: toJson($taskData));
                $this->access()->updateTask($task);
            }
        }
    }

    public function withdrawTask(string $taskId, string $operator): Task
    {
        $hist = $this->access()->getHistTask($taskId);
        AssertHelper::notNull($hist, "指定的历史任务[id=" . $taskId . "]不存在");
        if ($hist->isPerformAny()) {
            $tasks = $this->access()->getNextActiveTasks($hist->getId());
        } else {
            $tasks = $this->access()->getNextActiveTasksX($hist->getOrderId(), $hist->getTaskName(), $hist->getParentTaskId());
        }
        if (empty($tasks)) {
            throw new WorkFlowException("后续活动任务已完成或不存在，无法撤回.");
        }
        foreach ($tasks as $task) {
            $this->access()->deleteTask($task);
        }
        $task = $hist->undoTask();
        $task->setId(StringHelper:: getPrimaryKey());
        $task->setCreateTime(DateHelper:: getTime());
        $this->access()->saveTask($task);
        $this->assignTask($task->getId(), $task->getOperator());
        return $task;
    }

    public function createNewTask(string $taskId, int $taskType, string $actors): array
    {
        $task = $this->access()->getTask($taskId);
        AssertHelper::notNull($task, "指定的任务[id=" . $taskId . "]不存在");
        $tasks = [];
        try {
            $newTask = clone $task;
            $newTask->setTaskType($taskType);
            $newTask->setCreateTime(DateHelper:: getTime());
            $newTask->setParentTaskId($taskId);
            $tasks[] = $this->saveTask($newTask, $actors);
        } catch (\Exception $e) {
            throw new WorkFlowException("任务对象不支持复制", $e->getMessage());
        }
        return $tasks;
    }

    public function getTaskModel(string $taskId): TaskModel
    {
        $task = $this->access()->getTask($taskId);
        AssertHelper::notNull($task);
        $order = $this->access()->getOrder($task->getOrderId());
        AssertHelper:: notNull($order);
        $engine    = new WorkFlowEngine();
        $process   = $engine->process()->getProcessById($order->getProcessId());
        $model     = $process->getModel();
        $nodeModel = $model->getNode($task->getTaskName());
        AssertHelper:: notNull($nodeModel, "任务id无法找到节点模型.");
        if ($nodeModel instanceof TaskModel) {
            return $nodeModel;
        } else {
            throw new WorkFlowException("任务id找到的节点模型不匹配");
        }
    }
}
